/*
 * Spoof Airport MAC address
 *
 * Copyright (c) 2008 Michael Santos <michael.santos@gmail.com>
 *
 */

#include "airspoof.h"

extern char *__progname;
int verbose = 0;

    int
main(int argc, char *argv[])
{
    u_char mac[ETHER_ADDR_LEN]; 
    char *path = NULL;

    int ch  = 0;
    int run = 0;

    (void)memset(mac, 0, sizeof(mac));

    while ( (ch = getopt(argc, argv, "hnRv")) != -1) {
        switch (ch) {
            case 'n':
                run = 1;
                break;

            case 'R':
                sp_reload(KEXT_DIR);
                exit(EXIT_SUCCESS);

            case 'v':
                verbose++;
                break;

            case 'h':
            default:
                usage();
        }
    }

    argc -= optind;
    argv += optind;

    /* The MAC address must begin with 00: */
    switch (argc) {
        case 0:
            if (RAND_pseudo_bytes(mac + 1, ETHER_ADDR_LEN - 1) == 0)
                errx(EXIT_FAILURE, "%s", ERR_error_string(ERR_get_error(), NULL));
            break;
        case 1:
            if (sscanf(argv[0], "%*02x:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx",
                    &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]) != 5)
            usage();
            break;
        default:
            usage();
    }

    print_MAC(mac);

    path = sp_copy(TMPDIR);
    sp_patch(mac);

    if (run != 0)
        errx(EXIT_SUCCESS, "Not installing modified kernel extension: %s", path);

    sp_move(AIRPORT_PATCHED, AIRPORT_DRIVER);
    sp_reload(path);
    sp_cleanup(path);

    free(path);

    exit (EXIT_SUCCESS);
}

    void
print_MAC(u_char *buf)
{
    int i = 0;

    (void)fprintf(stderr, "Setting MAC address = ");
    for (i = 0; i < ETHER_ADDR_LEN - 1; i++)
        (void)fprintf(stderr, "%2x:", buf[i]);
    (void)fprintf(stderr, "%2x\n", buf[i]);
}

    char *
sp_copy(char *tmpl)
{
    char *path = NULL;
    char *tmp = NULL;

    IS_NULL(tmp = calloc(MAXPATHLEN, 1));
    (void)strlcpy(tmp, tmpl, MAXPATHLEN);

    IS_NULL(path = strdup(mkdtemp(tmp)));
    VERBOSE(1, "working path = %s\n", path);
    IS_ERR(chdir(path));
    SYSTEM("/bin/cp -pR /System/Library/Extensions/AppleAirPort2.kext .");

    free(tmp);

    return (path);
}

    void
sp_patch(u_char *mac)
{
    FILE *fp = NULL;
    FILE *wp = NULL;
    char buf[sizeof(search)];
    char byte;

    long pos = 0;
    int i = 0;
    int n = 0;

    if ( (fp = fopen(AIRPORT_DRIVER, "r")) == NULL)
        err(EXIT_FAILURE, "fopen");
    if ( (wp = fopen(AIRPORT_PATCHED, "w")) == NULL)
        err(EXIT_FAILURE, "fopen");

    while (fread(&byte, 1, 1, fp) == 1) {
        if ( (n == 0) && (byte == search[0])) {
            pos = ftell(fp);

            if (ungetc(byte, fp) == EOF)
                err(EXIT_FAILURE, "ungetc");

            FREAD(buf, sizeof(buf), 1, fp);

            if (memcmp (buf, search, sizeof(buf)) != 0) {
                (void)fseek(fp, pos, SEEK_SET);
            }
            else {
                VERBOSE(1, "Found magic bytes: %x\n", byte);
                for (i = 0; i < sizeof(buf); i++) {
                    if (patch[i] == 0x0A) {
                        VERBOSE(1, "Placing search[%d] = %02x\n", i, mac[n]);
                        FWRITE(&mac[n++], 1, 1, wp);
                        continue;
                    }
                    VERBOSE(1, "Writing byte: %x\n", patch[i]);
                    FWRITE(&patch[i], 1, 1, wp);
                }
                continue;
            }
        }

        FWRITE(&byte, 1, 1, wp);
    }

    if ( (feof(fp) == 0) || (ferror(fp) != 0) || (n != ETHER_ADDR_LEN))
        errx(EXIT_FAILURE, "Patching failed: %d", n);

    (void)fclose(fp);
    (void)fclose(wp);
}

    void
sp_move(char *src, char *dst)
{
    IS_ERR(unlink(dst));
    IS_ERR(link(src, dst));
    IS_ERR(unlink(src));
}

    void
sp_reload(char *path)
{
    if (path)
        IS_ERR(chdir(path));
    SYSTEM("/sbin/kextunload AppleAirPort2.kext");
    SYSTEM("/sbin/kextload AppleAirPort2.kext");
}

    void
sp_cleanup(char *path)
{
    char *cwd = NULL;
    char *cmd = NULL;
    int rv = 0;

    if (!path)
        errx(EXIT_FAILURE, "Must supply path to remove");

    IS_NULL(cwd = getcwd(NULL, 0));

    if (strcmp(cwd, path) == 0)
        IS_ERR(chdir(".."));

    if ( (path[0] != '/') || (strlen(path) < 6))
        errx(EXIT_FAILURE, "Malformed path");

    VERBOSE(1, "Clean up: removing working directory: %s\n", path);
    IS_NULL(cmd = calloc(MAXPATHLEN, 1));
    rv = snprintf(cmd, MAXPATHLEN, "/bin/rm -rf %s", path);

    if ( (rv < 1) || (rv > MAXPATHLEN))
        errx(EXIT_FAILURE, "Supplied path too long");

    VERBOSE(2, "Clean up: running command: %s\n", cmd);
    SYSTEM(cmd);

    free(cwd);
}

    void
usage(void)
{
    (void)fprintf(stderr, "%s: [-h|-n|-R|-v] <MAC Address>\n", __progname);
    (void)fprintf(stderr, "\tno arg\tGenerate a random MAC address\n");
    (void)fprintf(stderr, "\t00:11:22:33:44:55\tSet the MAC address to provided value\n");
    (void)fprintf(stderr, "\n\t-h\tUsage\n");
    (void)fprintf(stderr, "\t-n\tDo not actually change the MAC address\n");
    (void)fprintf(stderr, "\t-R\tRestore the original MAC address\n");
    (void)fprintf(stderr, "\t-v\tIncrease verbosity\n");

    exit (EXIT_FAILURE);
}
